﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using GapJunctionSimulation.Repositories;
using System.Threading;

namespace GapJunctionSimulation.BaseClasses
{
    public delegate void TissueChangedEventDelegate();
    class Tissue
    {
        public int limitrow;
        public bool StartedCalculating { get; set; }
        public List<Cell> Cells { get; private set; }
        public List<GapJunction> GapJunctions { get; private set; }
        private int _cellCount { get; set; }
        private int _cellRadius { get; set; }
        private LoggerRepository logger = LoggerRepository.Instance;
        private List<Cell> _stimulatedCells 
        {
            get
            {
                return Cells.Where(c=>c.Stimulated).ToList();
            }
        }

        public Tissue(int cellCount, int cellRadius)
        {
            Cells = new List<Cell>();
            GapJunctions = new List<GapJunction>();
            _cellCount = cellCount;
            _cellRadius = cellRadius;
        }

        public void CreateCells()
        {
            double start_x = 500, start_y = 300;
            double x = 500, y = 300;
            int cur_row = 0;
            int limit_row = (int)(((double)_cellCount).Sqrt() -
                (((double)_cellCount).Sqrt().Sqrt() + 2 * ((double)_cellCount).Sqrt().Sqrt().Sqrt()));
            limit_row = limit_row < 0 ? 0 : limit_row;  //min limit_row == 0
            limitrow = limit_row;
            for (int i = 0; i < _cellCount; i++)
            {
                Cell cell = new Cell() { Center = new Point() { X = x, Y = y }, Radius = _cellRadius, CellNumber = i };
                cell.CellChangedEvent += new CellChangedEventDelegate(cell_CellChangedEvent);
                if (RandomNumbersRepository.InstanceForCells.NextDoubleCells > (1 - (Configuration._probOfCellFailure / 100)))
                {
                    cell.Failed = true;
                }
                this.Cells.Add(cell);
                logger.LogAction(LoggerRepository.LogTypes.CellCreate, string.Format("cell number: {0}, X:{1} Y:{2}", i, x, y));
                if (cur_row == limit_row)
                {
                    if (i < _cellCount / 2)
                    {
                        limit_row = limit_row + 1;
                        y = start_y - cell.Radius;
                    }
                    else
                    {
                        limit_row = limit_row - 1;
                        y = start_y + cell.Radius;
                    }
                    x = x + (cell.Radius * Math.Sqrt(3));
                    start_x = x;
                    start_y = y;
                    cur_row = 0;
                }
                else
                {
                    y = y + cell.Radius * 2;
                    cur_row++;
                }
            }
            OnTissueChangedEvent();
        }

        void cell_CellChangedEvent()
        {
            OnTissueChangedEvent();
        }
        public event TissueChangedEventDelegate TissueChangedEvent;
        private void OnTissueChangedEvent()
        {
            if (TissueChangedEvent != null)
            {
                TissueChangedEvent();
            }
        }
        public void PrintCells(Form form) 
        {
            var cells = this.Cells;
            foreach (var cell in cells)
            {
                cell.Print(form, false);
            }
        }

        public void CalculateCellDependencies()
        {
            foreach (var cell in Cells)
            {
                var sb = new StringBuilder();
                foreach (var cell2 in Cells)
                {
                    if (cell != cell2)
                    {
                        var distanceLine = new Line(cell.Center, cell2.Center);
                        if (distanceLine.Length <= ( _cellRadius * 2 + 0.1))
                        {
                            cell.AdjacentCells.Add(cell2);
                            sb.Append(cell2.CellNumber);
                            sb.Append(' ');
                        }
                    }
                }
                logger.LogAction(LoggerRepository.LogTypes.CellAdjacency
                    , string.Format("Cell:{0} is adjacent to cells: {1}", cell.CellNumber, sb.ToString()));
            }
        }

        public void HandeGapJunctions(double time)
        {
            if (StartedCalculating)
            {
                return;
            }
            if (time < Configuration._gapJunctionHandleTime)
            {
                return; //todo: add these up
            }
            var numberOfIterations = (int)Math.Floor((double)time / Configuration._gapJunctionHandleTime);
            var threads = new List<Thread>();
            for (int i = 0; i < numberOfIterations; i++)
            {
                if (true) //switch true to disable multi threading, multi threading does not work right now. I dont know the problem
                {
                    foreach (var cell in _stimulatedCells)
                    {
                        HandleGapJunctionsInternal(cell);
                    }
                }
                else
                {
                    foreach (var cell in _stimulatedCells)
                    {
                        var thread = new Thread(() => HandleGapJunctionsInternal(cell));
                        threads.Add(thread);
                        thread.Start();
                    }

                    foreach (var thread in threads)
                    {
                        thread.Join();
                    }
                }                
            }
            OnTissueChangedEvent();
        }

        private void HandleGapJunctionsInternal(Cell cell)
        {
            var randomNumberRepository = RandomNumbersRepository.InstanceForCells;
            if (cell.Failed == true || cell.attemptedToOpenGAP == true || cell.NumberOfAdjacentCells <= 0)
            {
                return;
            }
            cell.attemptedToOpenGAP = true;  
            // Number of gap junctions          
            for (int i = 0; i < Configuration._numberOfGJperCell; i++)
            {
                if (randomNumberRepository.NextDoubleCells > (Configuration._probOfGapJunction / 100))
                {
                    logger.LogAction(LoggerRepository.LogTypes.GapJunctionCreation, string.Format("Cell {0} failed to create GAP juction", cell.CellNumber));
                    continue;
                } 
                var gapJunctionlessUnstimulatedNeighbourCells = cell.AdjacentCells.Where(c => c.HasGapJunctionsWithCell(cell) == false && c.Stimulated == false).ToList();
                if (gapJunctionlessUnstimulatedNeighbourCells.Count == 0)
                {
                    logger.LogAction(LoggerRepository.LogTypes.GapJunctionCreation, string.Format("Cell {0} failed to create GAP juction due to neighbouring cells", cell.CellNumber));
                    return;   // if no one to open a gap junction do not bother
                }
                var randInt = randomNumberRepository.NextIntCells(0, gapJunctionlessUnstimulatedNeighbourCells.Count);
                var gapJunction = new GapJunction(cell, gapJunctionlessUnstimulatedNeighbourCells[randInt]);
                GapJunctions.Add(gapJunction);
                Dashboard.numberOfGapJunctions++;
                gapJunction.StimulateCell();
                logger.LogAction(LoggerRepository.LogTypes.CellStimulation,
                    string.Format("Cell {0} stimulated cell {1}", gapJunction.Source.CellNumber, gapJunction.Destination.CellNumber));
                
            }
            
            OnTissueChangedEvent();
              
        }

        public void StartCalculating()
        {   
            this.StartedCalculating = true;
        }

        public void HandleCalculations(double time)
        {
            if (this.StartedCalculating == false)
            {
                return;
            }
            // assume each calculation takes one second
            var calculationTime = Configuration._calculationDuration;
            var numberOfIterations = Math.Floor((double)time/calculationTime);
            for (int i = 0; i < numberOfIterations; i++)
            {
                foreach (var cell in _stimulatedCells.Where(c=>c.LastCellInSequence == true).ToList())  //get only last cells 
                {
                    var selfResult = cell.Calculate(Configuration._mean, Configuration._stdDev);
                    var cellsOpenedGAPJunctionsToThisCell = cell.GapJunctions.Where(g=>g.Destination == cell).Select(g => g.Source).ToList();
                    
                    if (cellsOpenedGAPJunctionsToThisCell.Count == 0)   //i.e. serhat cell
                    {
                        // todo: do something
                    }
                    else
                    {
                        foreach (var c in cellsOpenedGAPJunctionsToThisCell)
                        {
                            c.PassedNumbers.Add(selfResult);
                            if (c.IsWaitingForAnotherCell == false)
                            {
                                c.LastCellInSequence = true;
                            }
                        }
                        cell.LastCellInSequence = false;        // this cell is processed, dont take it into account
                    }
                }
            }
            OnTissueChangedEvent();
        }
    }
}
